﻿using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Swimj.Core.Extensions;

namespace Swimj.Core.Utilities
{
    public static class CheckHelper
    {
        private static readonly string argumentNullExceptionMessage = "Parameter '{0}' in Method '{1}' is null" + Environment.NewLine + "Method '{1}' was called from '{2}'";

        public static T NotNull<T>(
        T value,
        [NotNull] string parameterName)
        {
            if (value == null)
            {
                throw new ArgumentNullException(parameterName);
            }

            return value;
        }

        public static T NotNull<T>(
            T value,
            [NotNull] string parameterName,
            string message)
        {
            if (value == null)
            {
                throw new ArgumentNullException(parameterName, message);
            }

            return value;
        }

        public static string NotNull(
            string value,
            [NotNull] string parameterName,
            int maxLength = int.MaxValue,
            int minLength = 0)
        {
            if (value == null)
            {
                throw new ArgumentException($"{parameterName} can not be null!", parameterName);
            }

            if (value.Length > maxLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName);
            }

            if (minLength > 0 && value.Length < minLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName);
            }

            return value;
        }

        public static string NotNullOrWhiteSpace(
            string value,
            [NotNull] string parameterName,
            int maxLength = int.MaxValue,
            int minLength = 0)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentException($"{parameterName} can not be null, empty or white space!", parameterName);
            }

            if (value.Length > maxLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName);
            }

            if (minLength > 0 && value.Length < minLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName);
            }

            return value;
        }

        public static string NotNullOrEmpty(
            string value,
            [NotNull] string parameterName,
            int maxLength = int.MaxValue,
            int minLength = 0)
        {
            if (string.IsNullOrEmpty(value))
            {
                throw new ArgumentException($"{parameterName} can not be null or empty!", parameterName);
            }

            if (value.Length > maxLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName);
            }

            if (minLength > 0 && value.Length < minLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName);
            }

            return value;
        }

        public static ICollection<T> NotNullOrEmpty<T>(
            ICollection<T> value,
            [NotNull] string parameterName)
        {
            if (value == null || !value.Any())
            {
                throw new ArgumentException(parameterName + " can not be null or empty!", parameterName);
            }

            return value;
        }

        public static Type AssignableTo<TBaseType>(
            Type type,
            [NotNull] string parameterName)
        {
            NotNull(type, parameterName);

            if (!type.IsAssignableTo(typeof(TBaseType)))
            {
                throw new ArgumentException($"{parameterName} (type of {type.AssemblyQualifiedName}) should be assignable to the {typeof(TBaseType).FullName}!");
            }

            return type;
        }

        public static string Length(
            string value,
            [NotNull] string parameterName,
            int maxLength,
            int minLength = 0)
        {
            if (minLength > 0)
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentException(parameterName + " can not be null or empty!", parameterName);
                }

                if (value.Length < minLength)
                {
                    throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName);
                }
            }

            if (value != null && value.Length > maxLength)
            {
                throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName);
            }

            return value!;
        }


        public static T ThrowIfNull<T>(this T input, string paramName)
        {
            return input.ThrowIfNull<ArgumentNullException, T>(paramName);
        }

        public static T ThrowIfNull<TException, T>(this T input, string message) where TException : Exception, new()
        {
            if (input != null && input.NotNull())
            {
                return input;
            }

            var exception = typeof(TException).CreateInstance<TException>(message);
            throw exception!;
        }

        public static void Requires(bool condition, Func<Exception> exceptionCreateFactory)
        {
            if (!condition)
                throw exceptionCreateFactory();
        }

        public static void Requires<TException>(bool condition)
            where TException : Exception, new()
        {
            if (!condition)
                throw new TException();
        }

        public static void Requires<TException>(bool condition, string message)
            where TException : Exception
        {
            if (!condition)
                throw (TException)Activator.CreateInstance(typeof(TException), message)!;
        }

        public static void NotNull(Expression<Func<object>> expression1, Expression<Func<object>> expression2,
            params Expression<Func<object>>[] parameters)
        {
            foreach (var parameterName in from expression in parameters.Concat(new[] { expression1, expression2 })
                                          let parameterToCheck = expression.Compile()()
                                          where parameterToCheck == null
                                          select expression.GetMemberName())
                throw new ArgumentNullException(parameterName, string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod()?.Name, ReflectionHelper.GetCallingMethod(1)?.Name));
        }

        public static T NotNull<T>(Expression<Func<T>> parameter)
        {
            var parameterToCheck = parameter.Compile()();
            if (parameterToCheck == null)
            {
                var parameterName = parameter.GetMemberName();
                throw new ArgumentNullException(parameterName, string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod()?.Name, ReflectionHelper.GetCallingMethod(1)?.Name));
            }
            return parameterToCheck;
        }

        public static void NotNull(object parameter, string parameterName)
        {
            if (parameter == null)
                throw new ArgumentNullException(parameterName, string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod()?.Name, ReflectionHelper.GetCallingMethod(1)?.Name));
        }

        public static void NotNull(Guid parameter, string parameterName)
        {
            if (parameter == Guid.Empty)
                throw new ArgumentNullException(parameterName, string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod()?.Name, ReflectionHelper.GetCallingMethod(1)?.Name));
        }

        public static void NotNullOrEmpty(string parameter, string parameterName)
        {
            if (string.IsNullOrEmpty(parameter))
                throw new ArgumentNullException(string.Format(argumentNullExceptionMessage, parameterName, ReflectionHelper.GetCallingMethod()?.Name, ReflectionHelper.GetCallingMethod(1)?.Name));
        }


        public static void IsVistaOrHigher()
        {
            if (!(Environment.OSVersion.Version.Major >= 6))
                throw new InvalidOperationException("Invalid OperatingSystem");
        }

        public static TResult TryCatch<TResult, TException>(Func<TResult> block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            try
            {
                return block();
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }
            return default(TResult)!;
        }

        public static async Task TryCatchAsync<TResult, TException>(Func<TResult> block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            await Task.Run(() => TryCatch(block, onException));
        }

        public static async Task TryCatchAsync<TException>(Action block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            await Task.Run(() => TryCatch(block, onException));
        }

        public static async Task<TResult> TryCatchAsync<TResult, TException>(Task<TResult> task,
            Func<TException, Exception>? onException = null, CancellationToken cancellation = default(CancellationToken))
            where TException : Exception
        {
            try
            {
                return await task;
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
                return default(TResult)!;
            }
        }

        public static async Task TryCatchAsync<TException>(Task block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            try
            {
                await block;
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }
        }

        public static void TryCatch<TException>(Action block, Func<TException, Exception>? onException = null)
            where TException : Exception
        {
            try
            {
                block();
            }
            catch (TException e)
            {
                if (onException != null)
                    throw onException(e);
            }
        }
    }
}

